package db

import (
	"fmt"
)

// nodeBucket is a bucket mapping integer nid to []byte name.
type nodeBucket struct {
	*bucketTx
}

// nameBucket is a bucket mapping []byte name to integer nid.
type nameBucket struct {
	*bucketTx
}

// GetName returns the name assigned to n or an error if n is not present.
func (b *nodeBucket) GetName(n nid) ([]byte, error) {
	name := b.Get(n.toBytes())
	if name == nil {
		return nil, b.Errorf("Unable to resolve nid %d", n)
	}
	return name, nil
}

// GetNid returns the nid assigned to name or an error if name is not present.
func (b *nameBucket) GetNid(name []byte) (nid, error) {
	n := b.Get(name)
	if n == nil {
		return 0, b.Errorf("Unable to resolve name %s", string(name))
	}
	return nid(btoi64(n)), nil
}

// NodeName is a bucket pair which allows for bijective mapping from []byte name 
// to/from integer nid.
type NodeName struct {
	*nodeBucket
	*nameBucket
}

type bucketOpener func([]string) ([]*bucketTx, error)

func openNodeName(basename string, f bucketOpener) (*NodeName, error) {
	names := []string{basename + "node", basename + "name"}
	if btxs, err := f(names); err != nil {
		return nil, err
	} else {
		return &NodeName{
			nodeBucket: &nodeBucket{btxs[0]},
			nameBucket: &nameBucket{btxs[1]},
		}, nil
	}
}

func OpenOrCreateNodeName(bsnk BucketSink, basename string) (*NodeName, error) {
	return openNodeName(basename, func (names []string) ([]*bucketTx, error) {
		return openOrCreateBucketTxs(bsnk, names)
	})
}

func OpenNodeName(bsrc BucketSource, basename string) (*NodeName, error) {
	return openNodeName(basename, func (names []string) ([]*bucketTx, error) {
		return openBucketTxs(bsrc, names)
	})
}

func (b *NodeName) CheckErr() error {
	if b.nodeBucket == nil {
		return fmt.Errorf("uninitialized node bucket")
	}
	if b.nodeBucket.err != nil {
		return b.nodeBucket.err
	}
	if b.nameBucket == nil {
		return fmt.Errorf("uninitialized name bucket")
	}
	if b.nameBucket.err != nil {
		return b.nameBucket.err
	}
	return nil
}

// GetOrAdd returns the existing nid for name if it's already present, or
// creates entries for them if not.  Returns the existing or new nid.
func (nn *NodeName) GetOrAdd(name []byte) (nid, error) {
	if bn := nn.nameBucket.Get(name); bn != nil {
		return nid(btoi64(bn)), nil
	} else {
		return nn.add(name)
	}
}

// Add creates an entry for name, returning the new nid.  It is an error to
// add an already existing name.
func (nn *NodeName) Add(name []byte) (nid, error) {
	if nn.nameBucket.Get(name) != nil {
		return 0, nn.nameBucket.Errorf("can't add name '%s', already exists", string(name))
	} else {
		return nn.add(name)
	}
}

// add allocates a new nid and binds it to name.  Do not use this function,
// use GetOrAdd or Add instead.
func (nn *NodeName) add(name []byte) (nid, error) {
	// Note that when NextSequence returns no error, the sequence value
	// is always > 0, thus we can treat 0 as an invalid value.
	nextseq, err := nn.nodeBucket.NextSequence()
	if err != nil {
		return 0, nn.nodeBucket.Errorf("can't get node id: %v", err)
	}
	n := nid(nextseq)
	if err = nn.nodeBucket.PutKeyInt64(int64(n), name); err != nil {
		return 0, err
	}
	if err = nn.nameBucket.PutKeyString(name, n.toBytes()); err != nil {
		return 0, err
	}
	return n, nil
}

// ForEach calls f for each (nid,name) pair in the NodeName.
func (nn *NodeName) ForEach(f func(k nid, v []byte) error) error {
	return nn.nodeBucket.ForEach(func(k, v []byte) error {
		return f(nid(btoi64(k)), v)
	})
}

// Lookup all names and store the nids for any that are present.  Any absent
// names will have the corresponding entry in nids untouched.
// Returns the index of the first name not found, or -1 if all are present.
func (nn *NodeName) GetNidsPresent(names [][]byte, nids []nid) int {
	firstMissing := -1
	for i, name := range names {
		if bn := nn.nameBucket.Get(name); bn != nil {
			nids[i] = nid(btoi64(bn))
		} else if firstMissing == -1 {
			firstMissing = i
		}
	}
	return firstMissing
}

// ordBucket is a bucket mapping {ord,nid} to nothing, where ord is an int64.
// Ord values need not be unique as nid is part of the key; however, because nid
// is part of the key, if there exist duplicate ord values, they will be ordered
// by nid, which may have unintended effects.
type ordBucket struct {
	*bucketTx
}

// rordBucket is a bucket mapping nid to ord, where ord is an int64.
type rordBucket struct {
	*bucketTx
}

// NodeQueue is a bucket pair which defines an ordering by int64 on nids.
// The ordBucket contains the ordering itself.  The rordBucket is used to
// allow updates to the ordering, by tracking the ord value for each nid.
type NodeQueue struct {
	*ordBucket
	*rordBucket
}

func openNodeQueue(basename string, f bucketOpener) (*NodeQueue, error) {
	names := []string{basename + "ord", basename + "rord"}
	if btxs, err := f(names); err != nil {
		return nil, err
	} else {
		return &NodeQueue{
			ordBucket: &ordBucket{btxs[0]},
			rordBucket: &rordBucket{btxs[1]},
		}, nil
	}
}

func OpenOrCreateNodeQueue(bsnk BucketSink, basename string) (*NodeQueue, error) {
	return openNodeQueue(basename, func (names []string) ([]*bucketTx, error) {
		return openOrCreateBucketTxs(bsnk, names)
	})
}

func OpenNodeQueue(bsrc BucketSource, basename string) (*NodeQueue, error) {
	return openNodeQueue(basename, func (names []string) ([]*bucketTx, error) {
		return openBucketTxs(bsrc, names)
	})
}

func (b *NodeQueue) CheckErr() error {
	if b.ordBucket == nil {
		return fmt.Errorf("uninitialized ord bucket")
	}
	if b.ordBucket.err != nil {
		return b.ordBucket.err
	}
	if b.rordBucket == nil {
		return fmt.Errorf("uninitialized rord bucket")
	}
	if b.rordBucket.err != nil {
		return b.rordBucket.err
	}
	return nil
}

// makeOrdKey produces a 16-byte key by concatenating the two 8-byte values given.
func makeOrdKey(ordb []byte, nidb []byte) []byte {
	ordkey := make([]byte, 16)
	copy(ordkey, ordb)
	copy(ordkey[8:], nidb)
	return ordkey
}

// GetOrd returns the ord value for the given n.
func (nq *NodeQueue) GetOrd(n nid) (int64, error) {
	if ordb := nq.rordBucket.Get(n.toBytes()); ordb == nil {
		return 0, nq.rordBucket.Errorf("nid=%d not found in queue", n)
	} else {
		return btoi64(ordb), nil
	}
}

// Add creates an entry in the queue for n with the given ord.
func (nq *NodeQueue) Add(n nid, ord int64) error {
	nb := n.toBytes()
	if nq.rordBucket.Get(nb) != nil {
		return nq.rordBucket.Errorf("can't add nid=%d, already exists", n)
	}

	ordb := i64tob(ord)
	ordkey := makeOrdKey(ordb, nb)
	if err := nq.ordBucket.Put(ordkey, []byte{}); err != nil {
		return nq.ordBucket.Errorf("can't put: %v", err)
	}
	if err := nq.rordBucket.Put(nb, ordb); err != nil {
		return nq.rordBucket.Errorf("can't put: %v", err)
	}
	return nil
}

// UpdateOrder assigns n the ordering value ord.
func (nq *NodeQueue) UpdateOrder(n nid, ord int64) error {
	// Get the refresh time in rurlq associated with n
	nb := n.toBytes()
	oldordb := nq.rordBucket.Get(nb)
	if oldordb == nil {
		return nq.rordBucket.Errorf("no entry found for nid=%d", n)
	}

	newordb := i64tob(ord)
	if err := nq.rordBucket.Put(nb, newordb); err != nil {
		return nq.rordBucket.Errorf("can't store nid=%d: %v", n, err)
	}

	// Rord[nb] exists with value oldordb so there ought to 
	// be an entry in Ord with key {oldordb}{nb}
	ordkey := makeOrdKey(oldordb, nb)
	if nq.ordBucket.Get(ordkey) == nil {
		return nq.ordBucket.Errorf("no entry found for nid=%d and ord=%d", n, btoi64(oldordb))
	}
	if err := nq.ordBucket.Delete(ordkey); err != nil {
		return nq.ordBucket.Errorf("error deleting nid=%d from urlq: %v", n, err)
	}

	if err := nq.ordBucket.Put(makeOrdKey(newordb, nb), []byte{}); err != nil {
		return err
	}
	return nil
}

type NodeNameQueue struct {
	*NodeName
	*NodeQueue
}

// QueueIfAbsent creates a node for name if there isn't one already,
// and stores it in the queue if not already present there.
func (nnq *NodeNameQueue) QueueIfAbsent(name []byte, ord int64) (nid, error) {
	n, err := nnq.NodeName.GetOrAdd(name)
	if err != nil {
		return 0, err
	}

	if _, err := nnq.NodeQueue.GetOrd(n); err != nil {
		if err := nnq.NodeQueue.Add(n, ord); err != nil {
			return 0, err
		}
	}
	return n, nil
}

// UpdateOrAdd creates a node for name if there isn't one already,
// creates an entry in the queue for it if it's not there already,
// and if the node is already present updates the existing value.
func (nnq *NodeNameQueue) UpdateOrAdd(name []byte, ord int64) (nid, error) {
	n, err := nnq.NodeName.GetOrAdd(name)
	if err != nil {
		return 0, err
	}

	if _, err := nnq.NodeQueue.GetOrd(n); err != nil {
		if err := nnq.NodeQueue.Add(n, ord); err != nil {
			return 0, err
		}
	} else {
		if err := nnq.NodeQueue.UpdateOrder(n, ord); err != nil {
			return 0, err
		}
	}

	return n, nil
}

type NidNameOrd struct {
	n nid
	name []byte
	ord int64
}

// Applies f to each node in the queue in queue order.  As soon as f returns
// a non-nil error, iteration stops and that error is returned.
func (nnq *NodeNameQueue) ForEach(f func(nno NidNameOrd) error) error {
	c := nnq.NodeQueue.ordBucket.Cursor()
	for k, _ := c.First(); k != nil; k, _ = c.Next() {
		ord, n := btoi64(k[:8]), nid(btoi64(k[8:]))
		if name, err := nnq.GetName(n); err != nil {
			return err
		} else {
			if err := f(NidNameOrd{n, name, ord}); err != nil {
				return err
			}
		}
	}
	return nil
}

// Return the first items in the queue, up to limit of them.
func (nnq *NodeNameQueue) FirstN(limit int) []NidNameOrd {
	ret := make([]NidNameOrd, 0, limit)
	nnq.ForEach(func(nno NidNameOrd) error {
		if len(ret) >= limit {
			return fmt.Errorf("reached limit")
		}
		ret = append(ret, nno)
		return nil
	})
	return ret
}

type EdgeBucket struct {
	*bucketTx
}

func (e *EdgeBucket) SetEdges(from nid, to []nid) error {
	return e.Put(from.toBytes(), i64stob(len(to), to))
}

// getEdges returns the edgelist for n.  If n has never been visited
// an error is returned.
func (e *EdgeBucket) GetEdges(n nid) ([]nid, error) {
	v := e.Get(n.toBytes())
	if v == nil {
		return nil, e.Errorf("nid=%v not found", n)
	}
	return btonids(v), nil
}

func btonid(b []byte) nid {
	return nid(btoi64(b))
}

func btonids(b []byte) []nid {
	nids := make([]nid, len(b)/8)
	btoi64s(b, nids)
	return nids
}

func (e *EdgeBucket) ForEachNodeEdges(f func(k nid, v []nid) error) error {
	return e.ForEach(func(k, v []byte) error {
		return f(btonid(k), btonids(v))
	})
}

type Graph map[nid][]nid

func (e *EdgeBucket) GetGraph() (Graph, error) {
	g := make(Graph, e.Stats().KeyN)
	// we could make this more efficient by allocating a single pool of
	// of nids for all the values slices, though that can provide unexpected
	// behaviour when attempting to append to them ... TODO try out the
	// 3-argument range operator to address this
	err := e.ForEachNodeEdges(func(k nid, v []nid) error {
		g[k] = v
		return nil
	})
	if err != nil {
		return nil, err
	}
	return g, nil
}

type NodeGraph struct {
	*NodeName
	*EdgeBucket
}
